/* Includes ------------------------------------------------------------------*/
#include <stdarg.h>
#include <string.h>
#include <stddef.h>
#include <stdio.h>

#include "cJSON.h"

#include "sotaOnenet.h"
#include "http.h"
#include "nril.h"
#include "fcntl.h"
#include "compare.h"
#include "nlog.h"


/* typedef -------------------------------------------------------------------*/
/* define --------------------------------------------------------------------*/
#define ONENET_SOTA_TX_BUFFER_SIZE   	1024
#define ONENET_SOTA_RX_BUFFER_SIZE   	1560
#define ONENET_SOTA_SINGLE_RX_COUNT		768
#define SOTA_OTA_HOST       "ota.heclouds.com"
							
/* macro ---------------------------------------------------------------------*/
/* variables -----------------------------------------------------------------*/
const struct sockaddr_at addrSota= {.sat_port=80, .sat_addr=SOTA_OTA_HOST};
bool _isDownloading = false;
int filePackage = -1;
/* function prototypes -------------------------------------------------------*/
extern void NEDelay_Ms(int n);

/* functions -----------------------------------------------------------------*/
__attribute__((weak)) void SotaOnenetHook_Start(){}
__attribute__((weak)) void SotaOnenetHook_Finish(){}
__attribute__((weak)) void SotaOnenetHook_Exit(){}
__attribute__((weak)) void SotaOnenetHook_Error(struct SotaOnenet *sotaOnenet, int errorCode){}

	
int sotaOnenet_RxAnalyse(struct SotaOnenet *sotaOnenet, char *message, int length);
int SotaOnenet_Transmit(struct SotaOnenet *sota, 
						char *message, 
						size_t messageLength, 
						char *bufferRx, 
						size_t bufferRxLength);
uint8_t htob(char *string ,size_t len);

bool sotaOnenet_Downloading()
{	return _isDownloading;	}



/*********************************************************************************************

  * @brief 		SotaOnenet_Init
  * @param   	sotaOnenet, sotaOnenet obj
				socket, socket Id
				pid, product id string
				deviceId, product id string
				version, current version of software
				auth, sota auth
  * @return 	
  * @remark 

  ********************************************************************************************/
void SotaOnenet_Init(struct SotaOnenet *sotaOnenet, int socket, char *pid, char *deviceId, char *version, char *auth)
{

    /* clear stack pool */
    memset(sotaOnenet, 0, sizeof(struct SotaOnenet));
    
    sotaOnenet->socket = socket;
    sotaOnenet->pid = pid;
    sotaOnenet->deviceId = deviceId;
    sotaOnenet->version = version;
    sotaOnenet->auth = auth;
    
    MD5_Init(&(sotaOnenet->md5Obj));
}

/*********************************************************************************************

  * @brief  SotaOnenet_Process
  * @param  *ptr, the pointer of sota object
  * @return 
  * @remark the main process of onenet sota, include
  *         0. get device id
  *         1. report current version of device
  *         2. check if there are new task, and get task information
  *         3. get the update package download token
  *         4. download the entire package
  *         5. verify md5 check if the package is correct
  *         6. report the result of download

  ********************************************************************************************/
#define SOTA_STRING_SIZE    128
#define ERROR_OCCUR(i)  {errorCode=i; goto __end;}   

void SotaOnenet_Process(void *ptr)
{
    char *message = NULL, *packetRx = NULL;
    char range[20] = ""; 
    int errorCode = 0, countRead = 0, sizeRead = 0;
	struct SotaOnenet *sotaOnenet = (struct SotaOnenet *)ptr;
    
	SotaOnenetHook_Start();
	
    message = (char *)malloc(ONENET_SOTA_TX_BUFFER_SIZE);
	packetRx = (char *)malloc(ONENET_SOTA_RX_BUFFER_SIZE);
    if(message == NULL)
    {   ERROR_OCCUR(0); } 
    
    NINFO("\n---------start sota-------------\n");
    
    /* step 0, get onenet id by device id */
    if(sotaOnenet->_onenetId[0] ==  '\0')
    {   
		Http_Create(message, ONENET_SOTA_TX_BUFFER_SIZE, "GET", SOTA_OTA_HOST, 80, "/ota/devInfo");
		Http_AddHeader(message, ONENET_SOTA_TX_BUFFER_SIZE, "Content-Type: application/json");
		Http_AddHeader(message, ONENET_SOTA_TX_BUFFER_SIZE, "Authorization: %s", sotaOnenet->auth);
		Http_EditContent(message, ONENET_SOTA_TX_BUFFER_SIZE, "{\"pid\":\"%s\", \"authInfo\":\"%s\"}", sotaOnenet->pid, sotaOnenet->deviceId);
		Http_FillContentLength(message, ONENET_SOTA_TX_BUFFER_SIZE, 0);
		
		connect(sotaOnenet->socket, (struct sockaddr const *)&addrSota, sizeof(addrSota));
		if(SotaOnenet_Transmit(sotaOnenet, message, ONENET_SOTA_TX_BUFFER_SIZE, packetRx, ONENET_SOTA_RX_BUFFER_SIZE) != 0)
		{	ERROR_OCCUR(1);	}
        sotaOnenet->process = SOTA_REPORT_VERSION;
        //disconnect(sotaOnenet->socket);
    }
    
    if(sotaOnenet->_onenetId[0] == '\0')
    {   ERROR_OCCUR(1); }
    
	/* step1: post, report device current version */
	Http_Create(message, ONENET_SOTA_TX_BUFFER_SIZE, "POST", SOTA_OTA_HOST, 80, "/ota/device/version?dev_id=%s", sotaOnenet->_onenetId);
	Http_AddHeader(message, ONENET_SOTA_TX_BUFFER_SIZE, "Content-Type: application/json");
	Http_AddHeader(message, ONENET_SOTA_TX_BUFFER_SIZE, "Authorization: %s", sotaOnenet->auth);
	Http_EditContent(message, ONENET_SOTA_TX_BUFFER_SIZE, "{\"s_version\":\"%s\"}", sotaOnenet->version);
	Http_FillContentLength(message, ONENET_SOTA_TX_BUFFER_SIZE, 0);
	
    connect(sotaOnenet->socket, (struct sockaddr const *)&addrSota, sizeof(addrSota));
    if(SotaOnenet_Transmit(sotaOnenet, message, ONENET_SOTA_TX_BUFFER_SIZE, packetRx, ONENET_SOTA_RX_BUFFER_SIZE) != 0)
	{	ERROR_OCCUR(2);	}
    sotaOnenet->process = SOTA_CHECK_TASK;
    
    /* step2: get, check update task and get package infomation */
	Http_Create(message, ONENET_SOTA_TX_BUFFER_SIZE, "GET", SOTA_OTA_HOST, 80, 
				"/ota/south/check?dev_id=%s&manuf=100&model=10001&type=2&version=%s&cdn=true", sotaOnenet->_onenetId, sotaOnenet->version);
	Http_AddHeader(message, ONENET_SOTA_TX_BUFFER_SIZE, "Content-Type: application/json");
	Http_AddHeader(message, ONENET_SOTA_TX_BUFFER_SIZE, "Authorization: %s", sotaOnenet->auth);
    //connect(sotaOnenet->socket, (struct sockaddr const *)&addrSota, sizeof(addrSota));
    if(SotaOnenet_Transmit(sotaOnenet, message, ONENET_SOTA_TX_BUFFER_SIZE, packetRx, ONENET_SOTA_RX_BUFFER_SIZE) != 0)
	{	ERROR_OCCUR(3);	}
    sotaOnenet->process = SOTA_CHECK_TOKEN;
    
    if(sotaOnenet->_token[0] == '\0')
    {   ERROR_OCCUR(0); }
    
    /* step3: get, check token*/
	Http_Create(message, ONENET_SOTA_TX_BUFFER_SIZE, "GET", SOTA_OTA_HOST, 80, "/ota/south/download/%s/check/?dev_id=%s", sotaOnenet->_token, sotaOnenet->_onenetId);
	Http_AddHeader(message, ONENET_SOTA_TX_BUFFER_SIZE, "Content-Type: application/json");
	Http_AddHeader(message, ONENET_SOTA_TX_BUFFER_SIZE, "Authorization: %s",  sotaOnenet->auth);
    //connect(sotaOnenet->socket, (struct sockaddr const *)&addrSota, sizeof(addrSota));
    if(SotaOnenet_Transmit(sotaOnenet, message, ONENET_SOTA_TX_BUFFER_SIZE, packetRx, ONENET_SOTA_RX_BUFFER_SIZE) != 0)
	{	ERROR_OCCUR(4);	}
    sotaOnenet->process = SOTA_DOWNLOAD;
    
    /* step4: create file and download from server*/
	if(strncmp(sotaOnenet->_target, sotaOnenet->_targetResume, sizeof(sotaOnenet->_target)) == 0)
	{	sotaOnenet->_sizeDownloaded = sotaOnenet->resumeIndex;	}
	
	if(sotaOnenet->_sizeDownloaded == 0)
	{	filePackage = open("packageOta", O_CREAT | O_TRUNC | O_WRONLY);	}
	else
	{	filePackage = open("packageOta", O_CREAT | O_WRONLY);	}
	if(filePackage < 0)
	{	ERROR_OCCUR(filePackage);	}
	if(sotaOnenet->_sizeDownloaded != 0)
	{	lseek(filePackage, sotaOnenet->_sizeDownloaded, SEEK_SET);	}
	_isDownloading = true;
	
    while(1)
    {
		Http_Create(message, ONENET_SOTA_TX_BUFFER_SIZE, "GET", SOTA_OTA_HOST, 80, "/ota/south/download/%s", sotaOnenet->_token);
		Http_AddHeader(message, ONENET_SOTA_TX_BUFFER_SIZE, "Content-Type: application/json");
		Http_AddHeader(message, ONENET_SOTA_TX_BUFFER_SIZE,  "Authorization: %s", sotaOnenet->auth);
		sprintf(range, "Range: %d-%d", sotaOnenet->_sizeDownloaded, sotaOnenet->_sizeDownloaded + ONENET_SOTA_SINGLE_RX_COUNT - 1);
		Http_AddHeader(message, ONENET_SOTA_TX_BUFFER_SIZE, range);
		
        //connect(sotaOnenet->socket, (struct sockaddr const *)&addrSota, sizeof(addrSota));
        if(SotaOnenet_Transmit(sotaOnenet, message, ONENET_SOTA_TX_BUFFER_SIZE, packetRx, ONENET_SOTA_RX_BUFFER_SIZE) != 0)
		{	ERROR_OCCUR(-1);	}
        if(sotaOnenet->_sizeDownloaded >= sotaOnenet->_sizePackage)
        {   break;  }
        if(sotaOnenet->_sizeDownloaded < 0)
        {   ERROR_OCCUR(-1);  }
		NINFO("[OnenetSota] download size update to %d/%d\n", sotaOnenet->_sizeDownloaded, sotaOnenet->_sizePackage);
    }
	_isDownloading = false;
	sotaOnenet->process = SOTA_REPORT_STATUS;
	close(filePackage);
	filePackage = -1;
	
	/* step6, md5 verify successfully, send code 201 to server */
	filePackage = open("packageOta", O_RDONLY);
	do
    {
		countRead = read(filePackage, message, ONENET_SOTA_SINGLE_RX_COUNT);
        MD5_Update(&(sotaOnenet->md5Obj), (uint8_t *)message, countRead);
		sizeRead += countRead;
    }while(countRead != 0);
    MD5_Final(&(sotaOnenet->md5Obj), sotaOnenet->md5Final);
	close(filePackage);
	filePackage = -1;
    
    if(Array_IsSame(sotaOnenet->_md5, sotaOnenet->md5Final, 16))
    {
		NINFO("[OnenetSota] md5 verify successfully , report status\n");
		Http_Create(message, ONENET_SOTA_TX_BUFFER_SIZE, "POST", SOTA_OTA_HOST, 80, "/ota/south/device/download/%s/progress?dev_id=%s", sotaOnenet->_token, sotaOnenet->_onenetId);
		Http_AddHeader(message, ONENET_SOTA_TX_BUFFER_SIZE, "Content-Type: application/json");
		Http_AddHeader(message, ONENET_SOTA_TX_BUFFER_SIZE,  "Authorization: %s", sotaOnenet->auth);
		Http_EditContent(message, ONENET_SOTA_TX_BUFFER_SIZE, "{\"step\":%d}", 201);
		Http_FillContentLength(message, ONENET_SOTA_TX_BUFFER_SIZE, 0);
        //connect(sotaOnenet->socket, (struct sockaddr const *)&addrSota, sizeof(addrSota));
        if(SotaOnenet_Transmit(sotaOnenet, message, ONENET_SOTA_TX_BUFFER_SIZE, packetRx, ONENET_SOTA_RX_BUFFER_SIZE) != 0)
		{	ERROR_OCCUR(-2);	}
        
		SotaOnenetHook_Finish();
    }
	else
	{	NINFO("[OnenetSota] md5 verify failed , exit process\n");	}
    
__end:
	if(filePackage > 0)
	{	close(filePackage);	}
	if(errorCode != 0)
	{	SotaOnenetHook_Error(sotaOnenet, errorCode);	}
	_isDownloading = false;	
	
    disconnect(sotaOnenet->socket);                     //  close connect 
	closesocket(sotaOnenet->socket);					// close socket
    if(message != NULL)
    {   free(message);   }
	if(packetRx != NULL)
	{	free(packetRx); }
    free(sotaOnenet);
    sotaOnenet = NULL;
    NINFO("\n---------end sota-------------\n");
	SotaOnenetHook_Exit();	
}
/*********************************************************************************************

  * @brief  SotaOnenet_Transmit
  * @param  sota,           the pointer of sota object
  *         message,        the pointer of the message which send to sota server
  *         messageLength,  size of message
  *         bufferRx,       the buffer which use for receive the response of server
  *         bufferLength,   size of the buffer
  * @return 
  * @remark 

  ********************************************************************************************/
int SotaOnenet_Transmit(struct SotaOnenet *sota, 
						char *message, 
						size_t messageLength, 
						char *bufferRx, 
						size_t bufferRxLength)
{
	int i = 0, j = 0, result;
	
	for(j=0; j<3; j++)
	{
		NDEBUG("%s\n", message);
		if(send(sota->socket, message, strlen(message), 0) >= 0)
		{	
			memset(bufferRx, 0, bufferRxLength);										
			for(i=0; i<200; i++)																		
			{																						
				if(recv(sota->socket, (void *)bufferRx, bufferRxLength, 0) > 0)  	
				{    
					NDEBUG("%s\n", bufferRx);
					break;	
				}                                               						
				NEDelay_Ms(100);																	
			}																						
			if(i != 200)																				
			{	
				result = sotaOnenet_RxAnalyse(sota, bufferRx, ONENET_SOTA_RX_BUFFER_SIZE);
				if(result >= 0)	
				{	return result;	}
			}	
		}		
		NINFO("[OnenetSota] retransmit %d times\n", j+1);
	}	
	
	return -1;
}
/*********************************************************************************************

  * @brief  sotaOnenet_RxAnalyse
  * @param  sotaOnenet,     the pointer of sota object
  *         message,        the pointer of the packet which receive from sota server
  *         length,         size of message
  * @return 
  * @remark 

  ********************************************************************************************/
int sotaOnenet_RxAnalyse(struct SotaOnenet *sotaOnenet, char *message, int length)
{
    char *p = NULL, *errorInfo = NULL;
    cJSON *json = NULL, *data= NULL, *jsonTemp= NULL;
    int errorCode = 0, result = 0;
    int32_t start, end, total;
    
	if((p = strstr(message, "HTTP/1.1")) == NULL)
    {   return  -1;  }
	
	sscanf(p, "HTTP/1.1 %d", &errorCode);
	
	if(errorCode != 200 && errorCode != 206)
	{	return -1;	}
	
	
    if((p = strstr(message, "\r\n\r\n")) == NULL)
    {   return  -1;  }
    
    /* response always include json unless on download process */
    if(sotaOnenet->process != SOTA_DOWNLOAD)
    {
        if((p = strchr(p, '{')) == NULL)
        {    return  -1; }

        json = cJSON_Parse(p);
        
        if(json == NULL)
        {   return  -1;  }
        jsonTemp = json->child;
        
        /* convert content to json object, get key value */
        while(jsonTemp != NULL)
        {
            if(strcmp(jsonTemp->string, "errno") == 0)
            {   errorCode = jsonTemp->valueint;   }             
            
            if(strcmp(jsonTemp->string, "error") == 0)
            {   errorInfo = jsonTemp->valuestring; }             
            
            if(strcmp(jsonTemp->string, "data") == 0)
            {   data = jsonTemp->child; }  
            
            jsonTemp = jsonTemp->next;
        }
        
        /* response error is not 0, return */
        if(errorCode != 0)
        {   
            result = 1;
            goto __end; 
        }
        
        /*  according to sota current process, run private code 
            */
        switch(sotaOnenet->process)
        {
        case SOTA_GET_DEVICE_ID:    // get device id by device imei
            while(data != NULL)
            {
                if(strcmp(data->string, "dev_id") == 0)
                {   
                    memcpy(sotaOnenet->_onenetId, data->valuestring ,strlen(data->valuestring));
                    break;
                } 
                
                data = data->next;
            }
            break;
        case SOTA_CHECK_TASK:       // get token/size/target from response of check update task
            while(data != NULL)
            {
                if(strcmp(data->string, "token") == 0)
                {   memcpy(sotaOnenet->_token, data->valuestring ,strlen(data->valuestring));  } 
                if(strcmp(data->string, "md5") == 0)
                {   
                    memcpy(sotaOnenet->_md5String, data->valuestring ,strlen(data->valuestring));  
                    for(int i=0; i<16; i++)
                    {   sotaOnenet->_md5[i] = htob(sotaOnenet->_md5String + i * 2, 2);   }
                } 
                if(strcmp(data->string, "target") == 0)
                {   memcpy(sotaOnenet->_target, data->valuestring ,strlen(data->valuestring));  }
                if(strcmp(data->string, "size") == 0)
                {   sotaOnenet->_sizePackage = data->valueint;  }
                
                data = data->next;
            }
		
			int fileOtaParameter = open("otaParameter", O_CREAT | O_RDWR | O_TRUNC);
			if(fileOtaParameter > 0)
			{
				if(write(fileOtaParameter, p, strlen(p)) > 0)
				{	NINFO("[OnenetSota] parameter write successfully\n");	}
				else
				{	NWARN("[OnenetSota] parameter write failed\n");	}
			}
			else
			{	{	NWARN("[OnenetSota] parameter open failed\n");	}	}
			close(fileOtaParameter);
            break;
        }
    }
    /*on download process, we need analyse headers and handle content which is not string*/
    else
    {
        p = message;
        while(1)
        {
            p = strstr(p, "\r\n");
            if(p == NULL)
            {   break;  }
            p = p + 2;
            
            
            if(strncmp(p, "Content-Range:", strlen("Content-Range:")) == 0)
            {                 
                sscanf(p, "Content-Range: bytes %d-%d/%d", &start, &end, &total);
                sotaOnenet->_sizeDownloaded = end + 1;
                p = strstr(p, "\r\n\r\n");
                if(p == NULL)
                {   break;  }
                
                p += 4;
				lseek(filePackage, start, SEEK_SET);
                write(filePackage, (uint8_t *)p, end - start + 1);
                p += (end - start) + 1;     // jump content size
                break;
            }
            else
            {   sotaOnenet->_sizeDownloaded = -1;  }
        }
    }

__end:      
    if(json != NULL)
    {   cJSON_Delete(json); }
    
    return result;
}

/*********************************************************************************************

  * @brief  ThreadInit_sotaOnenet
  * @param   
  * @return 
  * @remark 

  ********************************************************************************************/
void SotaOnenet_ResumeBreak(struct SotaOnenet *sotaOnenet, char *target, int startIndex)
{
	memcpy(sotaOnenet->_targetResume, target, sizeof(sotaOnenet->_targetResume));
	sotaOnenet->resumeIndex = startIndex;
}
void SotaOnenet_SaveBreak(struct SotaOnenet *sotaOnenet, char *target, int *startIndex)
{
	if((sotaOnenet->_sizeDownloaded & 0x3F) == 0 && sotaOnenet->_sizeDownloaded > 0)
	{
		memcpy(target, sotaOnenet->_target, sizeof(sotaOnenet->_target));
		*startIndex = sotaOnenet->_sizeDownloaded;
	}
}
void SotaOnenet_ClearBreak(char *target, int *startIndex)
{
	*startIndex = 0;
}
/*********************************************************************************************

  * @brief  htob, hex to byte
  * @param  string, 
  * @return 
  * @remark 

  ********************************************************************************************/
__attribute__((weak)) uint8_t htob(char *string ,size_t len)
{
	uint8_t byte = 0;;
	
	len = (len > 2)? 2:len;
	
	for(int i=0; i<len; i++)
	{
		byte <<= 4;
	    if(string[i] >= '0' && string[i] <= '9')
		{   byte |= (uint8_t)(string[i] - '0');  }
		else if(string[i] >= 'A' && string[i] <= 'F')
		{   byte |= (uint8_t)(string[i] - 'A') + 10;  }
		else if(string[i] >= 'a' && string[i] <= 'f')
		{   byte |= (uint8_t)(string[i] - 'a') + 10;  }	
	}
    
    return byte;
}
